package com.tca.common.learning.oss.minio.bean;

import com.amazonaws.AmazonClientException;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.HttpMethod;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.*;
import com.amazonaws.util.IOUtils;
import com.amazonaws.util.StringUtils;
import com.tca.common.learning.oss.minio.config.OssProperties;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.*;

/**
 * aws-s3 通用存储操作 支持所有兼容s3协议的云存储: {阿里云OSS，腾讯云COS，七牛云，京东云，minio，ceph 等}
 *
 * @author zhoua
 * @date 2020/5/23 6:36 上午
 * @since 1.0
 */
@Slf4j
public class OssTemplate implements InitializingBean {

	private OssProperties ossProperties;

	private AmazonS3 amazonS3;

	public OssTemplate(OssProperties ossProperties) {
		this.ossProperties = ossProperties;
	}

	/**
	 * 创建bucket
	 * @param bucketName bucket名称
	 */
	@SneakyThrows
	public void createBucket(String bucketName) {
		if (!amazonS3.doesBucketExistV2(bucketName)) {
			amazonS3.createBucket((bucketName));
		}
	}

	/**
	 * 获取全部bucket
	 * <p>
	 *
	 * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/ListBuckets">AWS
	 * API Documentation</a>
	 */
	@SneakyThrows
	public List<Bucket> getAllBuckets() {
		return amazonS3.listBuckets();
	}

	/**
	 * @param bucketName bucket名称
	 * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/ListBuckets">AWS
	 * API Documentation</a>
	 */
	@SneakyThrows
	public Optional<Bucket> getBucket(String bucketName) {
		return amazonS3.listBuckets().stream().filter(b -> b.getName().equals(bucketName)).findFirst();
	}

	/**
	 * @description 获取桶的统计信息：文件总数量、文件总大小
	 * @author wangzefeng 王泽峰
	 * @date 2020-9-15 15:08
	 * @param bucketName: bucket名称
	 * @return java.util.Map<java.lang.String,java.lang.Object>
	 */
	@SneakyThrows
	public Map<String,Object> getBucketInfo(String bucketName) {
		// 文件总数量
		int objectCnt = amazonS3.listObjects(bucketName).getObjectSummaries().size();
		// 文件总大小
		long objectVolume = amazonS3.listObjects(bucketName).getObjectSummaries().stream().mapToLong(S3ObjectSummary::getSize).sum()/1024;

		Map<String,Object> bucketInfo = new HashMap<>();
		bucketInfo.put("bucketObjectCnt", objectCnt);
		bucketInfo.put("bucketUsage", objectVolume);

		return bucketInfo;
	}

	/**
	 * @param bucketName bucket名称
	 * @see <a href=
	 * "http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/DeleteBucket">AWS API
	 * Documentation</a>
	 */
	@SneakyThrows
	public void removeBucket(String bucketName) {
		amazonS3.deleteBucket(bucketName);
	}

	/**
	 * 根据文件前置查询文件
	 * @param bucketName bucket名称
	 * @param prefix 前缀
	 * @param recursive 是否递归查询
	 * @return S3ObjectSummary 列表
	 * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/ListObjects">AWS
	 * API Documentation</a>
	 */
	@SneakyThrows
	public List<S3ObjectSummary> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive) {
		ObjectListing objectListing = amazonS3.listObjects(bucketName, prefix);
		return new ArrayList<>(objectListing.getObjectSummaries());
	}

	/**
	 * 根据文件前置 分页查询文件
	 * @param bucketName bucket名称
	 * @param prefix 前缀
	 * @param recursive 是否递归查询
	 * @param maxKeys 一次最多返回的文件数
	 * @param marker 可选参数，指示在存储桶中从何处开始列出。查询下一页数据时，getNextMarker()获取下一页的标记位
	 * @return S3ObjectSummary 列表
	 * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/ListObjects">AWS
	 * API Documentation</a>
	 */
	@SneakyThrows
	public ObjectListing getBatchObjectsByPrefix(String bucketName,
														 String prefix,
														 boolean recursive,
														 Integer maxKeys,
														 String marker) {
		ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
		listObjectsRequest.setBucketName(bucketName);
		listObjectsRequest.setPrefix(prefix);
		listObjectsRequest.setDelimiter("/");

		if(null != maxKeys) {
			listObjectsRequest.setMaxKeys(maxKeys);
		}
		if(!StringUtils.isNullOrEmpty(marker)) {
			listObjectsRequest.setMarker(marker);
		}

		return amazonS3.listObjects(listObjectsRequest);
	}

	/**
	 * 获取文件外链
	 * @param bucketName bucket名称
	 * @param objectName 文件名称
	 * @param expires 过期时间 (小时)
	 * @return url
	 * @see AmazonS3#generatePresignedUrl(String bucketName, String key, Date expiration)
	 */
	@SneakyThrows
	public String getObjectURL(String bucketName, String objectName, Integer expires) {
		Date date = new Date();
		Calendar calendar = new GregorianCalendar();
		calendar.setTime(date);
		calendar.add(Calendar.HOUR, expires);
		URL url = amazonS3.generatePresignedUrl(bucketName, objectName, calendar.getTime());
		return url.toString();
	}

	/**
	 * 获取文件
	 * @param bucketName bucket名称
	 * @param objectName 文件名称
	 * @return 二进制流
	 * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/GetObject">AWS
	 * API Documentation</a>
	 */
	@SneakyThrows
	public S3Object getObject(String bucketName, String objectName) {
		S3Object o = null;
		try {
			o = amazonS3.getObject(bucketName, objectName);
		} catch (AmazonClientException e) {
			log.error("获取S3对象错误：", e);
		} finally {
			if (o != null) {
				o.close();
			}
		}
		return o;
	}

	/**
	 * 上传文件
	 * @param bucketName bucket名称
	 * @param objectName 文件名称
	 * @param stream 文件流
	 * @throws Exception
	 */
	public void putObject(String bucketName, String objectName, InputStream stream) throws Exception {
		putObject(bucketName, objectName, stream, stream.available(), "application/octet-stream");
	}

	/**
	 * 上传文件
	 * @param bucketName bucket名称
	 * @param objectName 文件名称
	 * @param stream 文件流
	 * @param size 大小
	 * @param contextType 类型
	 * .jpg 图片:image/jpeg
	 * .mp4 视频:video/mp4
	 * .txt 文本:text/plain
	 * .xlsx 表格:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
	 * .docx 文档:application/vnd.openxmlformats-officedocument.wordprocessingml.document
	 *
	 * @throws Exception
	 * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/PutObject">AWS
	 * API Documentation</a>
	 */
	public PutObjectResult putObject(String bucketName, String objectName, InputStream stream, long size,
			String contextType) throws Exception {
		byte[] bytes = IOUtils.toByteArray(stream);
		ObjectMetadata objectMetadata = new ObjectMetadata();
		objectMetadata.setContentLength(size);
		objectMetadata.setContentType(contextType);
		ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
		return amazonS3.putObject(bucketName, objectName, byteArrayInputStream, objectMetadata);
	}

	/***
	 * 获取文件上传url请求PUT路径
	 * @param bucketName
	 * @param objectName
	 * @return
	 */
	public String getUploadUrl(String bucketName, String objectName) {
		GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectName)
				.withMethod(HttpMethod.PUT);
		URL url = this.amazonS3.generatePresignedUrl(request);
		if (url == null) {
			return "";
		}

		return url.toString();
	}

	/**
	 * 上传文件或创建目录
	 * @param putObjectRequest 上传的文件或目录
	 * .jpg 图片:image/jpeg
	 * .mp4 视频:video/mp4
	 * .txt 文本:text/plain
	 * .xlsx 表格:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
	 * .docx 文档:application/vnd.openxmlformats-officedocument.wordprocessingml.document
	 *
	 * @throws Exception
	 * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/PutObject">AWS
	 * API Documentation</a>
	 */
	public PutObjectResult putObject(PutObjectRequest putObjectRequest) throws Exception {
		return amazonS3.putObject(putObjectRequest);
	}

	/**
	 * @description 上传文件或创建目录
	 * @author wangzefeng 王泽峰
	 * @date 2020-9-16 11:15
	 * @param bucketName:
	 * @param key:
	 * @param content:
	 * @return com.amazonaws.services.s3.model.PutObjectResult
	 */
	public PutObjectResult putObject(String bucketName, String key, String content) throws Exception {
		byte[] contentBytes = content.getBytes(StringUtils.UTF8);
		InputStream is = new ByteArrayInputStream(contentBytes);
		ObjectMetadata metadata = new ObjectMetadata();
		metadata.setContentType("");
		metadata.setContentLength(0);
		return putObject(new PutObjectRequest(bucketName, key, is, metadata));
	}

	/**
	 * @description 上传文件或创建目录
	 * @author wangzefeng 王泽峰
	 * @date 2020-9-16 11:15
	 * @param bucketName:
	 * @param key:
	 * @param content:
	 * @return com.amazonaws.services.s3.model.PutObjectResult
	 */
	public PutObjectResult writeObject(String bucketName, String key, String content) throws Exception {
		return amazonS3.putObject(bucketName,  key,  content);
	}

	/**
	 * 获取文件信息
	 * @param bucketName bucket名称
	 * @param objectName 文件名称
	 * @throws Exception
	 * @see <a href="http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/GetObject">AWS
	 * API Documentation</a>
	 * issues:https://github.com/aws/aws-sdk-java/issues/1405
	 */
	public S3Object getObjectInfo(String bucketName, String objectName) throws Exception {
		S3Object o = null;
		try {
			o = amazonS3.getObject(bucketName, objectName);
		} catch (AmazonClientException e) {
			log.error("获取S3对象错误：", e);
		} finally {
			if (o != null) {
				o.close();
			}
		}
		return o;
	}

	/**
	 * 删除文件
	 * @param bucketName bucket名称
	 * @param objectName 文件名称
	 * @throws Exception
	 * @see <a href=
	 * "http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/DeleteObject">AWS API
	 * Documentation</a>
	 */
	public void removeObject(String bucketName, String objectName) throws Exception {
		String delimiter = "/";
		if(!objectName.endsWith(delimiter)) {
			// 删除文件
			amazonS3.deleteObject(bucketName, objectName);
		} else {
			// 删除文件夹
			this.removeObjects(bucketName,objectName);
		}
	}

	/**
	 * 删除目录下的所有文件：文件和文件夹
	 * @param bucketName bucket名称
	 * @param objectName 文件夹名称
	 * @throws Exception
	 * @see <a href=
	 * "http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/DeleteObject">AWS API
	 * Documentation</a>
	 */
	public void removeObjects(String bucketName, String objectName) throws Exception {
		ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
		listObjectsRequest.setBucketName(bucketName);
		listObjectsRequest.setMaxKeys(100);
		listObjectsRequest.setPrefix(objectName);
		ObjectListing objectsResponse = amazonS3.listObjects(listObjectsRequest);

		while (true) {
			DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(bucketName);
			List<DeleteObjectsRequest.KeyVersion> keys = new ArrayList<>();

			for (Iterator<?> iterator = objectsResponse.getObjectSummaries().iterator(); iterator.hasNext(); ) {
				S3ObjectSummary s3Object = (S3ObjectSummary)iterator.next();
				keys.add(new DeleteObjectsRequest.KeyVersion(s3Object.getKey()));
			}
			deleteObjectsRequest.setKeys(keys);

			amazonS3.deleteObjects(deleteObjectsRequest);

			if (objectsResponse.isTruncated()) {
				objectsResponse = amazonS3.listObjects(listObjectsRequest);
				continue;
			}
			break;
		}
	}

	/**
	 * @description 复制文件
	 * @author wangzefeng 王泽峰
	 * @date 2020-10-20 17:46
	 * @param sourceBucketName: 源桶名称
	 * @param sourceKey: 源文件key
	 * @param destinationBucketName: 目标桶名称
	 * @param destinationKey: 目标文件key
	 * @return void
	 */
	public void copyObject(String sourceBucketName, String sourceKey,
						   String destinationBucketName, String destinationKey) throws Exception {
		String delimiter = "/";

		if(!sourceKey.endsWith(delimiter)) {
			// 1:复制文件
			amazonS3.copyObject( sourceBucketName,sourceKey,destinationBucketName, destinationKey);
		} else {
			// 2:复制文件夹
			this.copyObjects(sourceBucketName,sourceKey,destinationBucketName, destinationKey);
		}

	}

	/**
	 * @description 批量复制文件夹下的文件和子目录
	 * @author wangzefeng 王泽峰
	 * @date 2020-10-20 17:46
	 * @param sourceBucketName: 源桶名称
	 * @param sourceKey: 源文件key
	 * @param destinationBucketName: 目标桶名称
	 * @param destinationKey: 目标文件key
	 * @return void
	 */
	public void copyObjects(String sourceBucketName, String sourceKey,
						   String destinationBucketName, String destinationKey) throws Exception {

		// 目录下的文件复制
		amazonS3.copyObject( sourceBucketName,sourceKey,destinationBucketName, destinationKey);

		ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
		listObjectsRequest.setBucketName(sourceBucketName);
		listObjectsRequest.setMaxKeys(100);
		listObjectsRequest.setPrefix(sourceKey);
		ObjectListing objectsResponse = amazonS3.listObjects(listObjectsRequest);

		while (true) {
			for (Iterator<?> iterator = objectsResponse.getObjectSummaries().iterator(); iterator.hasNext(); ) {
				S3ObjectSummary s3Object = (S3ObjectSummary)iterator.next();
				String sourceKeyItem = s3Object.getKey();
				String destinationKeyItem =destinationKey+s3Object.getKey().substring(sourceKey.length());
				amazonS3.copyObject(sourceBucketName,sourceKeyItem,destinationBucketName, destinationKeyItem);
			}

			if (objectsResponse.isTruncated()) {
				listObjectsRequest.setMarker(objectsResponse.getNextMarker());
				objectsResponse = amazonS3.listObjects(listObjectsRequest);
				continue;
			}
			break;
		}

		//2：目录下的文件夹复制
		List<String> commonPrefixList = objectsResponse.getCommonPrefixes();
		if(commonPrefixList == null || commonPrefixList.size() == 0) {
			return;
		}
		for(String commonPrefixe : commonPrefixList) {
			sourceKey = commonPrefixe;
			destinationKey = destinationKey + commonPrefixe.substring(objectsResponse.getPrefix().length());
			this.copyObjects(sourceBucketName, sourceKey,destinationBucketName, destinationKey);
		}
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		ClientConfiguration clientConfiguration = new ClientConfiguration();
		AwsClientBuilder.EndpointConfiguration endpointConfiguration =
				new AwsClientBuilder.EndpointConfiguration(ossProperties.getEndpoint(), ossProperties.getRegion());
		AWSCredentials awsCredentials = new BasicAWSCredentials(ossProperties.getAccessKey(),
				ossProperties.getSecretKey());
		AWSCredentialsProvider awsCredentialsProvider = new AWSStaticCredentialsProvider(awsCredentials);
		this.amazonS3 = AmazonS3Client.builder()
				.withEndpointConfiguration(endpointConfiguration)
				.withClientConfiguration(clientConfiguration)
				.withCredentials(awsCredentialsProvider)
				.disableChunkedEncoding()
				.withPathStyleAccessEnabled(ossProperties.getPathStyleAccess())
				.build();
	}

}
